/*
Copyright 2024 MySQL Parser Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

import (
	"fmt"
	"strings"
)

// SQLNode defines the interface for all nodes generated by the parser
type SQLNode interface {
	// Format formats the node
	Format(buf *TrackedBuffer)
	// String returns a string representation of the node
	String() string
}

// Statement represents a SQL statement
type Statement interface {
	SQLNode
	iStatement()
}

// Expr represents an expression
type Expr interface {
	SQLNode
	iExpr()
}

// TableExpr represents a table expression
type TableExpr interface {
	SQLNode
	iTableExpr()
}

// SelectExpr represents a select expression
type SelectExpr interface {
	SQLNode
	iSelectExpr()
}

// TrackedBuffer is used to rebuild a query from the ast
type TrackedBuffer struct {
	*strings.Builder
	bindLocations []BindLocation
}

// BindLocation represents the location of a bind variable
type BindLocation struct {
	Offset, Length int
}

// NewTrackedBuffer creates a new TrackedBuffer
func NewTrackedBuffer(buf *strings.Builder) *TrackedBuffer {
	if buf == nil {
		buf = &strings.Builder{}
	}
	return &TrackedBuffer{
		Builder: buf,
	}
}

// WriteString writes a string to the buffer
func (buf *TrackedBuffer) WriteString(s string) {
	buf.Builder.WriteString(s)
}

// WriteByte writes a byte to the buffer
func (buf *TrackedBuffer) WriteByte(b byte) {
	buf.Builder.WriteByte(b)
}

// Myprintf writes a formatted string to the buffer
func (buf *TrackedBuffer) Myprintf(format string, values ...interface{}) {
	fmt.Fprintf(buf.Builder, format, values...)
}

// String returns the string representation of any SQLNode
func String(node SQLNode) string {
	if node == nil {
		return "<nil>"
	}
	buf := NewTrackedBuffer(nil)
	node.Format(buf)
	return buf.String()
}

// IdentifierCI represents a case-insensitive identifier
type IdentifierCI struct {
	val string
}

// NewIdentifierCI creates a new case-insensitive identifier
func NewIdentifierCI(str string) IdentifierCI {
	return IdentifierCI{val: str}
}

// String returns the string representation
func (node IdentifierCI) String() string {
	return String(node)
}

// Format formats the identifier
func (node IdentifierCI) Format(buf *TrackedBuffer) {
	buf.WriteString(node.val)
}

// IsEmpty returns true if the identifier is empty
func (node IdentifierCI) IsEmpty() bool {
	return node.val == ""
}

// Lowered returns the lowercase version
func (node IdentifierCI) Lowered() string {
	return strings.ToLower(node.val)
}

// Equal compares two identifiers case-insensitively
func (node IdentifierCI) Equal(other IdentifierCI) bool {
	return strings.EqualFold(node.val, other.val)
}

// IdentifierCS represents a case-sensitive identifier
type IdentifierCS struct {
	val string
}

// NewIdentifierCS creates a new case-sensitive identifier
func NewIdentifierCS(str string) IdentifierCS {
	return IdentifierCS{val: str}
}

// String returns the string representation
func (node IdentifierCS) String() string {
	return String(node)
}

// Format formats the identifier
func (node IdentifierCS) Format(buf *TrackedBuffer) {
	buf.WriteString(node.val)
}

// IsEmpty returns true if the identifier is empty
func (node IdentifierCS) IsEmpty() bool {
	return node.val == ""
}

// Equal compares two identifiers case-sensitively
func (node IdentifierCS) Equal(other IdentifierCS) bool {
	return node.val == other.val
}

// TableName represents a table name with optional qualifier
type TableName struct {
	Name      IdentifierCS
	Qualifier IdentifierCS
}

// NewTableName creates a new table name
func NewTableName(name string) TableName {
	return TableName{Name: NewIdentifierCS(name)}
}

// String returns the string representation
func (node TableName) String() string {
	return String(node)
}

// Format formats the table name
func (node TableName) Format(buf *TrackedBuffer) {
	if !node.Qualifier.IsEmpty() {
		node.Qualifier.Format(buf)
		buf.WriteByte('.')
	}
	node.Name.Format(buf)
}

// IsEmpty returns true if the table name is empty
func (node TableName) IsEmpty() bool {
	return node.Name.IsEmpty()
}

// ToViewName creates a view name from table name
func (node TableName) ToViewName() TableName {
	return node
}